#include "secuencial.h"

#define TAM_OCUPADO_CABECERA	8

Secuencial::Secuencial(std::string& nombre, unsigned int tam_bloque):ArchivoDatos(nombre, tam_bloque){
	std::string aux = nombre +EXT_EELL;
	espacios_libres = new ArchEspaciosLibres(aux,TAM_EELL);
	indice_secundario = NULL;
}

Secuencial::~Secuencial(){
	//Si el archivo esta abierto, lo cerramos
	this->cerrar();
	delete(espacios_libres);	
}
	
void Secuencial::abrir() throw(ExcepAbriendoArch){
	//Si el archivo de datos esta abierto, arrojo excep
	if (this->fd != -1)
		throw ExcepAbriendoArch("Error - El archivo: " + this->get_nombre() + " ya estaba abierto.");
	else {
		std::string dest = "./DATA/" + this->get_nombre();
		this->fd = ManejadorArchivos::get_instance()->abrir_archivo(dest);
		if ( this->fd == -1)
			throw ExcepAbriendoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
		espacios_libres->abrir(); //arroja una excepcion en caso de no poder abrirlo
		espacios_libres->agregar_observador(Buffer::get_instance());
		this-> cant_bloques = get_cant_bloques_header();
		
	}	
}
				
void Secuencial::crear(std::string formato) throw(ExcepCreandoArch, ExcepSintaxisParser, ExcepBloqueTam){
	if(this->get_tam_bloque() < TAM_OCUPADO_CABECERA)
		throw ExcepBloqueTam("Error - No se pudo crear el archivo. El tamaño del bloque es muy pequeno para la cabecera");
	else{
		try{
			Archivo::cargar_registro_base(formato);
			TipoStruct * reg = get_registro_base();
			if(reg->get_tamanio() == 0){
				throw ExcepSintaxisParser("Error - No se ingreso formato para el archivo");
			}
			std::string dest = "./DATA/" + this->get_nombre();
			this->fd = ManejadorArchivos::get_instance()->crear_archivo(dest);
			if ( this->fd == -1 )
				throw ExcepCreandoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
			espacios_libres->crear(); //arroja una excepcion en caso de no poder crearlo.
			espacios_libres->agregar_observador(Buffer::get_instance());
			//Crea el header, y le guarda el formato
			dest = this->get_nombre() + ".head";
			Header head(dest);
			head.crear();
			head.guardar(formato, this);
			head.cerrar();
			
			//Creo el bloque 0 de header y lo guardo
			Bloque* bloque_header = new Bloque(0, this->get_tam_bloque());
			Buffer::get_instance()->guardar_bloque(*this, bloque_header);
			delete (bloque_header);
			//Asigno que la cant de bloques es 1
			this->set_cant_bloques_header(1);
			this->cant_bloques = 1;
		}
		catch(ExcepSintaxisParser &e){
			throw e; //para hacerlo explicito
		}
	}	
}
		
void Secuencial::cerrar() {
	if(fd != -1){
		set_cant_bloques_header(this->cant_bloques);
		this->set_cambio();
		this->avisar_observadores(NULL);
		ManejadorArchivos::get_instance()->cerrar_archivo(this->fd);
		espacios_libres->cerrar();
		this->fd = -1;
	}
}
		
void Secuencial::ingresar(TipoStruct * reg) throw(ExcepArchCerrado,ExcepRegExistente, ExcepBloqueTam){
	
	if( fd != -1 ){
		Bloque* bloque_actual = NULL;
		if (es_unico(reg)){
			unsigned int tam = reg->get_tamanio_serializado();
			//Verifico que el registro tenga menor tamanio que el bloque
			if( tam <= this->get_tam_bloque() - TAM_OCUPADO_CABECERA){ 
				//consulto en el archivo de espacios libres donde me conviene ubicarlo
				int num_bloque = espacios_libres->consultar_espacio_libre(tam);		
				if (num_bloque != -1){
					num_bloque++; //Porque yo el 0 no lo meto en el de espacios libres
					//Obtengo el reg desde el buffer
					bloque_actual = Buffer::get_instance()->get_bloque(*this, num_bloque);
					//Pongo el registro en el bloque
					poner_reg_en_bloque(bloque_actual, reg);
				} 
				else{
					//Creo un bloque nuevo vacio
					bloque_actual = new Bloque(cant_bloques, this->get_tam_bloque());
					this->set_cant_regs_bloque(bloque_actual, 0);
					//agrego una entrada para un nuevo bloque en el arch de espacios libres
					this->espacios_libres->append(bloque_actual->get_espacio_libre());
					//Pongo el registro en el bloque
					poner_reg_en_bloque(bloque_actual, reg);
					cant_bloques++; //Incremento la cant de bloques del arch
				}
				delete (bloque_actual);
			}
			else{
				throw ExcepBloqueTam("Error - El dato a ingresar es mas grande que el tamano del bloque de " + this->get_nombre() + ". ");
			}	
		}
		else
			throw ExcepRegExistente("Error - El registro que intenta ingresar ya existe en el archivo. ");
	}
	else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");
	
}
		
void Secuencial::quitar(std::vector<ParesDatos*> &elementos, std::vector<TipoStruct*> &resultado) throw(ExcepArchCerrado){
	if (this-> fd != -1){
		Bloque* bloque_actual = NULL;
		unsigned int tam_quitado;
		std::vector<TipoStruct*> res_temp; //Para albergar los reg q se quitaron en cada bloque
		TipoStruct* reg_base = get_registro_base();
		for(unsigned int i=1; i< cant_bloques; i++){ //recorro todos los bloques
			try{
				bloque_actual = Buffer::get_instance()->get_bloque(*this,i);
				//Quito todos los regs del bloque que cumplen con lo pedido
				bloque_actual->quitar_desde(sizeof(unsigned int), reg_base, elementos, res_temp, tam_quitado);
				if (res_temp.size() != 0)
				{
					//Resto la cantidad de quitados a la cant de regs del bloque
					set_cant_regs_bloque(bloque_actual, get_cant_regs_bloque(bloque_actual) - res_temp.size());
					//actualizo el arch de espacios libres.Le paso el numero de bloque y lo que libere.
					espacios_libres->actualizar_espacio_libre(bloque_actual->get_num_bloque() - 1, tam_quitado);
					//Guardo el bloque en disco
					Buffer::get_instance()->guardar_bloque(*this, bloque_actual);
					//Agrego los resultados
					this->concatenar(resultado, res_temp);
				}
				if (get_cant_regs_bloque(bloque_actual) == 0) {
					this->eliminar_bloque(bloque_actual);
					i--;	
				}
			}catch (ExcepLeerBloque &e){
				if (bloque_actual != NULL) { 
					delete (bloque_actual); 
					bloque_actual = NULL;
				}
				throw e;	//la sigo propagando por ahora
			}
			if (bloque_actual != NULL) { 
						delete (bloque_actual); 
						bloque_actual = NULL;
			}
		}
	}else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");					 	
}

void Secuencial::consultar(std::vector<ParesDatos*> &consultas, std::vector<TipoStruct*> &resultado)
												 throw(ExcepArchCerrado,ExcepLeerBloque){
	//ESTAS DOS VBLES SIRVEN PARA OPTIMIZAR LA BUSQUEDA POR CLAVE
	bool por_clave = consulta_por_clave(consultas);
	bool clave_encontrada = false;
	unsigned int cant_regs_bloque = 0;
	
	unsigned int cont = 1;
	if (this-> fd != -1){
		Bloque* bloque_actual = NULL;
		TipoStruct* reg_base = get_registro_base();
		std::vector<TipoStruct*> res_temp;
		while ((cont < cant_bloques) && (!clave_encontrada)){ //recorro todos los bloques
			try{
				bloque_actual = Buffer::get_instance()->get_bloque(*this,cont);
				//Hago la consulta sobre todos los regs del bloque
				cant_regs_bloque = get_cant_regs_bloque(bloque_actual);
				if ( cant_regs_bloque != 0) {
					if (!por_clave) {
						bloque_actual->consultar_desde(sizeof(unsigned int), reg_base, consultas, res_temp);
						this->concatenar(resultado, res_temp);
					} else {
					
						TipoStruct* reg = (TipoStruct*)reg_base->clonar();
						unsigned int pos_res, size_res;
					
						clave_encontrada = bloque_actual->buscar_primero_desde(sizeof(unsigned int),reg, consultas, pos_res, size_res);
						if (clave_encontrada)
							resultado.push_back(reg);
						else
							delete (reg);
					}
				}
			}catch (ExcepLeerBloque &e){
				if (bloque_actual != NULL) { 
					delete (bloque_actual); 
					bloque_actual = NULL;
				}
				throw e;	//la sigo propagando por ahora
			}
			if (bloque_actual != NULL) { 
					delete (bloque_actual); 
					bloque_actual = NULL;
			}
			cont++;
		}
	}else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");					 	
}

void Secuencial::actualizar(TipoStruct * reg) throw(ExcepArchCerrado,ExcepRegInexistente, ExcepBloqueTam){
	if (this-> fd != -1){
		Bloque* bloque_actual = NULL;
		bool encontrado = false;
		TipoStruct* reg_base = (TipoStruct*)get_registro_base()->clonar();
		unsigned int nbloque = 1; //Arranco desde el bloque 1
		unsigned int pos_res = 0;
		unsigned int size_res = 0;
		while ((nbloque < cant_bloques) && (!encontrado)) {
			try{
				//Pido el bloque al buffer
				bloque_actual = Buffer::get_instance()->get_bloque(*this,nbloque);
				//Busco el reg dentro del bloque por clave
				encontrado = bloque_actual->buscar_por_clave_desde(sizeof(unsigned int), &((*reg)[0]),reg_base, pos_res, size_res);
				//Si encontre el reg
				if (encontrado) {
					//saco el reg del bloque
					sacar_reg_bloque(bloque_actual,NULL, pos_res, size_res);
					//pongo el nuevo registro, si no entra lo ingreso en otro lado
					if (!poner_reg_en_bloque(bloque_actual, reg))
						ingresar(reg);
				}
			} catch (ExcepLeerBloque &e){
					if (bloque_actual != NULL) { 
						delete (bloque_actual); 
						bloque_actual = NULL;
					}
					delete(reg_base);
					throw e;
			}
			if (bloque_actual != NULL) { 
						delete (bloque_actual); 
						bloque_actual = NULL;
			}
			nbloque++;
		}
		delete(reg_base);
		//Si no encontre el registro a actualizar tiro una excep de registro inexistente
		if (!encontrado)
			throw ExcepRegInexistente("Error - El registro a actualizar no existe en el archivo de datos. ");
	}else
		throw ExcepArchCerrado("Error - El archivo : "+ this-> get_nombre() +" esta cerrado. ");
}
 													
TiposArchivo Secuencial::get_tipo_archivo(){
	return SECUENCIAL;
}

void Secuencial::eliminar() {
	//Elimino el archivo de espacios libres
	espacios_libres->eliminar();
	//Elimino el archivo de header
	std::string header = "./DATA/"+get_nombre()+".head";
	remove(header.c_str());
	//Cierro el archivo
	this->cerrar();
	//Lo Elimino
	std::string mi_ruta = "./DATA/"+get_nombre();
	remove(mi_ruta.c_str()); 
}

unsigned int Secuencial::get_espacio_libre() throw(ExcepArchCerrado, ExcepLeerBloque){
	unsigned int tam_libre;
	
	tam_libre =  this->espacios_libres->get_espacio_libre_datos();
	
	//tam_libre += (this->get_tam_bloque() - 12);//le saco lo que ocupa la cantidad de bloques almacenada
		
	return tam_libre ;
}

unsigned int Secuencial::get_esp_libre_indice()  throw(ExcepArchCerrado, ExcepLeerBloque){
	return 0;	
}
		 										
unsigned int Secuencial::get_cant_regs_indice() throw(ExcepArchCerrado){
	return 0;	
}

long Secuencial::get_tam_arch_indice() throw(ExcepArchCerrado) {
	return 0;
}
 				
bool Secuencial::es_unico(TipoStruct * reg){
	bool _return = false;
	//pasamos el registro a un vector de ParesDato*, solo me interesa verficar su 
	//valor en el campo 0, la clave.
	std::vector<ParesDatos*> consulta;
	std::vector<TipoStruct*> resultado;
	TipoDato* dato_consulta = ((*reg)[0]).clonar();
	ParesDatos * par = new ParesDatos(0, dato_consulta);
	consulta.push_back( par ); 
	try{
		this->consultar(consulta,resultado);
		_return = (resultado.size() == 0); //quiere decir que no encontro el registro y es unico 
		std::vector<TipoStruct*>::iterator it = resultado.begin();
		while (it != resultado.end()) {
			if ((*it) != NULL)
				delete(*it);
			it++;
		}
		resultado.clear();	 	
	}catch(ExcepArchCerrado &e){
		delete (par);
		throw e;
	}
	catch(ExcepLeerBloque & e1){
	}
	delete (par);
	consulta.clear();
	return _return;		
}


 unsigned int Secuencial::get_cant_regs() throw(ExcepArchCerrado){
	
	unsigned int cont = 0;
	Bloque* bloque_actual;
	
	for(unsigned int i = 1; i < this->cant_bloques; i++ ){
		//obtengo el bloque nro i desde el buffer
		bloque_actual = Buffer::get_instance()->get_bloque(*this,i);
		//le pido al bloque la cant de registros contenidos en el mismo
		cont += get_cant_regs_bloque(bloque_actual);
		//Borro el bloque que me devolvio el buffer
		delete (bloque_actual);
	}
	return cont;
}

unsigned int Secuencial::get_cant_regs_bloque(Bloque* bloque){
	unsigned int result = 0;
	//Leo desde el bloque en la pos 0 la cant de registros
	bloque->leer((char*)&result, 0, sizeof(unsigned int));
	return (result);
}

void Secuencial::set_cant_regs_bloque(Bloque* bloque, unsigned int cant){
	//Escribo el bloque en la pos 0 con la cant de regs
	bloque->escribir((char*)&cant, 0, sizeof(unsigned int));
}
		
int Secuencial::sacar_reg_bloque(Bloque* bloque, TipoStruct * reg, unsigned int pos_reg, unsigned int tam_reg){
	//Saco el reg del bloque
	bloque->comprimir(pos_reg, tam_reg);
	//Obtengo la cant de regs en el bloque
	unsigned int cant;
	cant = get_cant_regs_bloque(bloque);
	//resto 1 a la cant y escribo en el bloque
	set_cant_regs_bloque(bloque, cant-1);
	//actualizo el arch de espacios libres.Le paso el numero de bloque y lo que libere
	espacios_libres->actualizar_espacio_libre(bloque->get_num_bloque() - 1,tam_reg);
	//lo devuelvo a disco despues de haber sacado el registro
	Buffer::get_instance()->guardar_bloque(*this,bloque);
	
	return 0;
}

bool Secuencial::poner_reg_en_bloque(Bloque* bloque, TipoStruct* reg){
	//Meto el reg en el bloque
	unsigned int tam_reg = bloque->append(reg);
	if(tam_reg == 0)
		return false ;
	//Obtengo la cant de regs en el bloque
	unsigned int cant;
	cant = get_cant_regs_bloque(bloque);
	//sumo 1 a la cant y escribo en el bloque
	set_cant_regs_bloque(bloque, cant+1);
	//actualizo el arch de espacios libres. Le paso el numero de bloque y lo que ocupe.
	espacios_libres->actualizar_espacio_libre(bloque->get_num_bloque() - 1, -tam_reg);
	//lo devuelvo a disco despues de haber puesto el reg
	Buffer::get_instance()->guardar_bloque(*this,bloque);
	return true;
}



bool Secuencial::consulta_por_clave(std::vector<ParesDatos*> &consultas){
	bool encontrado = false;
	std::vector<ParesDatos*>::iterator it = consultas.begin();
	while ((it != consultas.end()) && (!encontrado)) {
		ParesDatos* par = (*it);	
		if (par->get_numero_campo() == 0)
			encontrado = true;
		it++;
	}
	return encontrado;
}

void Secuencial::eliminar_bloque (Bloque* bloque){
	if (bloque != NULL) {
		unsigned int n_bloque = bloque->get_num_bloque();
		unsigned int ult_bloque = this->cant_bloques - 1;
		//Me fijo si no es el ultimo, entonces lo intercambio
		if (n_bloque != ult_bloque)
			this->intercambiar_bloque(n_bloque, ult_bloque);
		//Invalido el ultimo bloque en el buffer para q cuente como libre
		Buffer::get_instance()->invalidar_bloque(*this, ult_bloque);
		//Trunco el archivo para eliminar fisicamente el ultimo bloque
		unsigned int pos_trunc = ult_bloque * this->get_tam_bloque();		
		ManejadorArchivos::get_instance()->truncar_arch(this->get_fd(), pos_trunc);
		cant_bloques--;
		set_cant_bloques_header(cant_bloques);
		//Elimino la ultima entrada del archivo de espacios libres
		this->espacios_libres->borrar_ultimo();
	}
}

void Secuencial::intercambiar_bloque (unsigned int nbloque1, unsigned int nbloque2){
	Bloque* bloque1 = Buffer::get_instance()->get_bloque(*this, nbloque1);
	Bloque* bloque2 = Buffer::get_instance()->get_bloque(*this, nbloque2);
	
	bloque1->set_num_bloque(nbloque2);
	bloque2->set_num_bloque(nbloque1);
	
	Buffer::get_instance()->guardar_bloque(*this, bloque1);
	Buffer::get_instance()->guardar_bloque(*this, bloque2);
	
	this->espacios_libres->set_espacio_libre(nbloque1 - 1, bloque2->get_espacio_libre());
	this->espacios_libres->set_espacio_libre(nbloque2 - 1, bloque1->get_espacio_libre());
	
	delete(bloque1);
	delete(bloque2);
}
